﻿using DotNetty.Buffers;
using DotNetty.Codecs;
using DotNetty.Transport.Bootstrapping;
using DotNetty.Transport.Channels;
using DotNetty.Transport.Channels.Sockets;
using System;
using System.Threading.Tasks;

namespace DotNetty.Wraper
{
    class TcpSocketClientBuilder : BaseGenericClientBuilder<ITcpSocketClientBuilder, ITcpSocketClient, byte[]>, ITcpSocketClientBuilder
    {
        public TcpSocketClientBuilder(string ip, int port)
            : base(ip, port)
        {
            //this.AddLast(new Handlers.Logging.LoggingHandler());
        }
        protected Action<IChannelPipeline> _setEncoder { get; set; }

        public ITcpSocketClientBuilder SetLengthFieldDecoder(int maxFrameLength, int lengthFieldOffset, int lengthFieldLength, int lengthAdjustment, int initialBytesToStrip, ByteOrder byteOrder = ByteOrder.BigEndian)
        {
            this.AddLast(new LengthFieldBasedFrameDecoder(byteOrder, maxFrameLength, lengthFieldOffset, lengthFieldLength, lengthAdjustment, initialBytesToStrip, true));

            return this;
        }

        public ITcpSocketClientBuilder SetLengthFieldEncoder(int lengthFieldLength)
        {
           this.AddLast(new LengthFieldPrepender(lengthFieldLength));

            return this;
        }

        /// <summary>
        /// Inserts multiple <see cref="IChannelHandler"/>s at the last position of this pipeline.
        /// </summary>
        /// <param name="handlers"></param>
        /// <returns></returns>
        public ITcpSocketClientBuilder AddLast(params IChannelHandler[] handlers)
        {
            _setEncoder += x => x.AddLast(handlers);

            return this;
        }
        /// <summary>
        /// Appends an <see cref="Transport.Channels.IChannelHandler"/> at the last position of this pipeline.
        /// </summary>
        /// <param name="name"></param>
        /// <param name="handler"></param>
        /// <returns></returns>
        public ITcpSocketClientBuilder AddLast(string name, IChannelHandler handler)
        {
            _setEncoder += x => x.AddLast(name, handler);

            return this;
        }

        public async override Task<ITcpSocketClient> BuildAsync(Func<ITcpSocketClient, IChannelHandler> addChannelHandler = null, Action<Transport.Channels.IChannelPipeline> OnPipelineAction = null
            , bool useLibuv = false, System.Security.Cryptography.X509Certificates.X509Certificate2 tlsCertificate = null)
        {
            var tcpClient = new TcpSocketClient(_ip, _port, _event);
            string targetHost = null;
            if (tlsCertificate != null)
            {
                targetHost = tlsCertificate.GetNameInfo(System.Security.Cryptography.X509Certificates.X509NameType.DnsName, false);
            }
            IEventLoopGroup group;
            if (useLibuv)
            {
                group = new Transport.Libuv.EventLoopGroup();
            }
            else
            {
                group = new MultithreadEventLoopGroup();
            }
            var clientChannel = await new Bootstrap()
                .Group(group)
                .Channel<TcpSocketChannel>()
                .Option(ChannelOption.TcpNodelay, true)
                .Handler(new ActionChannelInitializer<IChannel>(channel =>
                {
                    var pipeline = channel.Pipeline;
                    if (!string.IsNullOrWhiteSpace(targetHost))
                    {
                        pipeline.AddLast(new Handlers.Tls.TlsHandler(stream => new System.Net.Security.SslStream(stream, true, (sender, certificate, chain, errors) => true), new Handlers.Tls.ClientTlsSettings(targetHost)));
                    }
                    _setEncoder?.Invoke(pipeline);
                    OnPipelineAction?.Invoke(pipeline);

                    if (addChannelHandler != null)
                        pipeline.AddLast("tcpClient", addChannelHandler(tcpClient));
                    else
                        pipeline.AddLast("tcpClient", new CommonChannelHandler(tcpClient));
                })).ConnectAsync($"{_ip}:{_port}".ToIPEndPoint());

            tcpClient.SetChannel(clientChannel);
            tcpClient.AfterClose = () =>
            {
                group.ShutdownGracefullyAsync(TimeSpan.FromMilliseconds(100), TimeSpan.FromSeconds(1)).Wait();
            };

            return await Task.FromResult(tcpClient);
        }
    }
}
